#include "Avalon/Codecs/ZLibEncoder.hpp"
#include <boost/scoped_array.hpp>
#include <zlib.h>
#include "Avalon/Codecs/EncoderException.hpp"
#include "Avalon/IO/Buffer.hpp"

using namespace Avalon;
using namespace Avalon::Codecs;
using namespace Avalon::IO;
using namespace boost;

ZLibEncoder::ZLibEncoder() {}

ZLibEncoder::~ZLibEncoder() {}

bool ZLibEncoder::IsEncodeInPlaceSupported() const {
  return false;
}

void ZLibEncoder::Encode(const void* source, int sourceSize, void* destination,
    int destinationSize, Out<int> encodedSize) {
  int minimumSizeRequired = static_cast<int>(1.1 * (sourceSize + 1) + 12 +
    sizeof(int));
  assert(destinationSize >= minimumSizeRequired);
  *reinterpret_cast<int*>(destination) = sourceSize;
  int size = destinationSize - sizeof(int);
  int result = compress(reinterpret_cast<Bytef*>(destination) + sizeof(int),
    reinterpret_cast<uLongf*>(&size), reinterpret_cast<const Bytef*>(source),
    sourceSize);
  if(result != Z_OK) {
    if(result == Z_BUF_ERROR) {
      BOOST_THROW_EXCEPTION(EncoderException(
        "The buffer was not large enough to hold the compressed data."));
    } else if(result == Z_MEM_ERROR) {
      BOOST_THROW_EXCEPTION(EncoderException("Insufficient memory."));
    } else {
      BOOST_THROW_EXCEPTION(EncoderException("Unknown error."));
    }
  }
  *encodedSize = size;
}

void ZLibEncoder::Encode(const void* source, int sourceSize,
    Buffer* destination, Out<int> encodedSize) {
  int size = static_cast<int>(1.1 * (sourceSize + 1) + 12);
  scoped_array<char> compressionBuffer(new char[size]);
  int result = compress(reinterpret_cast<Bytef*>(compressionBuffer.get()),
    reinterpret_cast<uLongf*>(&size), reinterpret_cast<const Bytef*>(source),
    sourceSize);
  if(result != Z_OK) {
    if(result == Z_BUF_ERROR) {
      BOOST_THROW_EXCEPTION(EncoderException(
        "The buffer was not large enough to hold the compressed data."));
    } else if(result == Z_MEM_ERROR) {
      BOOST_THROW_EXCEPTION(EncoderException("Insufficient memory."));
    } else {
      BOOST_THROW_EXCEPTION(EncoderException("Unknown error."));
    }
  }
  destination->Append(&sourceSize, sizeof(int));
  destination->Append(compressionBuffer.get(), size);
  *encodedSize = size + sizeof(int);
}
